package com.fm.jbsql.util;

import com.fm.jbsql.sql.DbDriverType;

import java.util.logging.Level;
import java.io.*;

/**
 * Created by IntelliJ IDEA.
 * User: syaroshenko
 * Date: Jul 13, 2006
 * Time: 11:36:27 AM
 * Class to manage command line arguments.
 */

/**
 * Class to manage command line arguments.
 */
public final class CmdOpt {
    private boolean bGen;
    private boolean bDryrun;
    private boolean bNolog;
    private boolean bSqllst;
    private boolean bSubdirs;
    private boolean bLogonly;
    private String serverName;
    private transient String userName;
    private transient String paswd;
    private String database;
    private String clientCharset;
    private String path;
    private String inputFileName;
    private Level loglevel;
    private DbDriverType driverType;
    private File entriesSource;
    private String outputFileName;
    private String columnSeparator;
    private String rowSeparator;
    private String revisionFileName;
    private String stringBefore;
    private String stringAfter;
    private boolean breakOnError;
    private String pathToInterfaces;

    /**
     * Constructor.
     */
    public CmdOpt() {
        bGen = false;
        bDryrun = false;
        bNolog = false;
        bSqllst = true;
        bSubdirs = false;
        bLogonly = false;
        path = null;
        loglevel = Level.OFF;
        driverType = DbDriverType.EDT_NONE;
        database = "";
        clientCharset = "utf8";
        inputFileName = null;
        outputFileName = null;
        revisionFileName = null;
        stringBefore = "";
        stringAfter = "";
        columnSeparator = "";
        rowSeparator = "";
        breakOnError = false;
        pathToInterfaces = null;
    }

    /**
     * Display help and usage.
     * @param bVersionOnly display only version
     * @return boolean
     */
    private static boolean displayHelp(boolean bVersionOnly) {
        if ( bVersionOnly ) System.out.println("Batch sql v0.3");
        else {
            //@todo Add userguide
            System.out.println("jbsql.jar v0.3");
            System.out.println("usage: jbsql.jar <parameters> <options>");
            System.out.println("where <parameters>:");
            System.out.println("\t-S<server IP-address and port, separated with ':'>");
            System.out.println("\t-U<user name>");
            System.out.println("\t-P<user password>");
            System.out.println("\t-D<database>");
            System.out.println("\t-C<driver type: sybase|mysql|mssql>");
            System.out.println("\t-J<client charset>");
            System.out.println("\t-I<path to interfaces file> (taken from env $SYBASE unless specified)");
            System.out.println("\t-i<input file>");
            System.out.println("\t-o<output file>");
            System.out.println("\t-col<column separator>");
            System.out.println("\t-rev<file name>");
            System.out.println("\t-b<string before> (applied to <-rev> parameter only)");
            System.out.println("\t-a<string after> (applied to <-rev> parameter only)");
            System.out.println("\t-p<work path>");
            System.out.println("\t-l<debug log level: SEVERE|INFO|FINEST>");
            System.out.println("<options>:");
            System.out.println("\t-h  display help");
            System.out.println("\t-v  display version");
            System.out.println("\t-g  generate 'sql.lst' file");
            System.out.println("\t-t  dry run");
            System.out.println("\t-y  only log into hst_bsql without compilation");
            System.out.println("\t-r  use svn entries file as source");
            System.out.println("\t-d  process 1-level subdirectories relative to path");
            System.out.println("\t-n  save no log in hst_bsql");
            System.out.println("\t-x  break execution on error (not implemented yet, always set to TRUE)");
            System.out.println("\t-row  new line for each row\n");
            System.out.println("examples:");
            System.out.println("Generate sql.lst file:");
            System.out.println("\tjbsql -g");
            System.out.println("Compile files from sql.lst:");
            System.out.println("\tjbsql -S10.100.3.4:6500 -Dopale2000 -Usa -Pvalerie -Csybase");
        }
        return false;
    }

    /**
     * Parse command line.
     * @param args command line arguments
     * @return boolean
     */
    public boolean parseCmdLine(String[] args) {
        String tmpath=".";
        for ( int i = 0; i < args.length; i++ ) {
            if ( args[i].equals("-h") ) return displayHelp(false);
            else if ( args[i].equals("-v") ) return displayHelp(true);
            else if ( args[i].startsWith("-S") ) serverName = args[i].substring(2);
            else if ( args[i].startsWith("-U") ) userName = args[i].substring(2);
            else if ( args[i].startsWith("-P") ) paswd = args[i].substring(2);
            else if ( args[i].startsWith("-D") ) database = args[i].substring(2);
            else if ( args[i].startsWith("-J") ) clientCharset = args[i].substring(2);
            else if ( args[i].startsWith("-I") ) pathToInterfaces = args[i].substring(2);
            else if ( args[i].startsWith("-p") ) tmpath = args[i].substring(2);
            else if ( args[i].startsWith("-i") ) inputFileName = args[i].substring(2);
            else if ( args[i].startsWith("-b") ) stringBefore = args[i].substring(2);
            else if ( args[i].startsWith("-a") ) stringAfter = args[i].substring(2);
            else if ( args[i].startsWith("-o") ) outputFileName = args[i].substring(2);
            else if ( args[i].startsWith("-col") ) columnSeparator = args[i].substring(4);
            else if ( args[i].startsWith("-row") ) rowSeparator = "\n";
            else if ( args[i].startsWith("-rev") ) {
                revisionFileName = args[i].substring(4);
                bSqllst = false;
            }
            else if ( args[i].equals("-t") ) bDryrun = true;
            else if ( args[i].equals("-n") ) bNolog = true;
            else if ( args[i].equals("-r") ) bSqllst = false;
            else if ( args[i].equals("-d") ) bSubdirs = true;
            else if ( args[i].equals("-g") ) bGen = true;
            else if ( args[i].equals("-y") ) bLogonly = true;
            else if ( args[i].equals("-x") ) breakOnError = true;
            else if ( args[i].startsWith("-l") ) {
                String lvl = args[i].substring(2);
                if ( lvl.equals("SEVERE") ) loglevel = Level.SEVERE;
                else if ( lvl.equals("INFO") ) loglevel = Level.INFO;
                else if ( lvl.equals("FINEST") ) loglevel = Level.FINEST;
                else loglevel = Level.OFF;
            }
            else if ( args[i].startsWith("-C") ) {
                String drv = args[i].substring(2);
                if ( drv.equals("sybase") ) driverType = DbDriverType.EDT_SYBASE;
                else if ( drv.equals("mysql") ) driverType = DbDriverType.EDT_MYSQL;
                else if ( drv.equals("mssql") ) driverType = DbDriverType.EDT_MSSQL;
                else {
                    System.out.println("ERROR: Invalid driver type.");
                    return displayHelp(false);
                }
            }
            else return displayHelp(false);
        }

        if ( !setPath(tmpath) ) return displayHelp(false);
        
        // Verify params integrity
        if ( args.length == 0 ) return displayHelp(false);
        if ( bSqllst && revisionFileName != null ) return displayHelp(false);
        if ( bSubdirs && (bGen || (revisionFileName != null || inputFileName != null)) ) return displayHelp(false);
        if ( driverType == DbDriverType.EDT_NONE || userName == null ) {
            if ( !(bGen || (bDryrun && bSqllst) || revisionFileName != null) ) return displayHelp(false);
        }

        if ( driverType == DbDriverType.EDT_NONE && userName != null ) return displayHelp(false);
        database = database == null ? "" : database;
        if ( driverType == DbDriverType.EDT_SYBASE) {
           try {convertServerAliasToAddress();}
           catch (JbsqlException ex) {
               return false;
           }
        }

        if ( path == null ) setPath(null);

        return true;
    }

    /**
     * Debug level.
     * @return Level
     */
    public Level getLevel() {
        return loglevel;
    }

    /**
     * Flag to generate sql.lst. Enabled if TRUE.
     * @return boolean
     */
    public boolean isGen() {
        return bGen;
    }

    /**
     * Flag to set "dry run" mode: list objects to compile. Enabled if TRUE.
     * @return boolean
     */
    public boolean isDryrun() {
        return bDryrun;
    }

    /**
     * Flag to save no log in hst_bsql. Enabled if TRUE.
     * @return boolean
     */
    public boolean isNolog() {
        return bNolog;
    }

    /**
     * Flag to choose entries source. sql.lst if TRUE, svn entries if FALSE.
     * @return boolean
     */
    public boolean isSqllst() {
        return bSqllst;
    }

    /**
     * Flag to process first level subdirectories. Enabled if TRUE.
     * @return boolean
     */
    public boolean isSubdirs() {
        return bSubdirs;
    }

    /**
     * Flag to set "log only" mode: save objects in hst_bsql without compilation. Enabled if TRUE.
     * @return boolean
     */
    public boolean isLogonly() {
        return bLogonly;
    }

    /**
     * Flag to break execution on error. Enabled if TRUE.
     * @return boolean
     */
    public final boolean isBreakOnError() {
        return breakOnError;
    }

    /**
     * Get driver type.
     * @return DbDriverType
     */
    public DbDriverType getDriverType() {
        return driverType;
    }

    /**
     * Get user password to access database.
     * @return String
     */
    public String getPaswd() {
        return paswd;
    }

    /**
     * Get database user name.
     * @return String
     */
    public String getUserName() {
        return userName;
    }

    /**
     * Get server.
     * @return String
     */
    public String getServerName() {
        return serverName;
    }

    /**
     * Get entries source.
     * @return File
     */
    public final File getEntriesSource() {
        return entriesSource;
    }

    /**
     * Get working path, under which project will be run.
     * @return String path
     */
    public String getPath() {
        return path;
    }

    /**
     * Get client charset.
     * @return String
     */
    public String getClientCharset() {
        return clientCharset;
    }

    /**
     * Get database.
     * @return String
     */
    public final String getDatabase() {
        return database;
    }

    /**
     * Get input file name to compile.
     * @return String
     */
    public final String getInputFileName() {
        return inputFileName;
    }

    /**
     * Get output file name to store query results.
     * @return String
     */
    public final String getOutputFileName() {
        return outputFileName;
    }

    /**
     * Get column separator.
     * @return String
     */
    public final String getColumnSeparator() {
        return columnSeparator;
    }

    /**
     * Get row separator.
     * @return String
     */
    public final String getRowSeparator() {
        return rowSeparator;
    }

    /**
     * Get file name, which is requested for svn-revision number.
     * @return String
     */
    public final String getFileNameAskingRevision() {
        return revisionFileName;
    }

    /**
     * Get string to append before query result.
     * @return String
     */
    public final String getStringBefore() {
        return stringBefore;
    }

    /**
     * Get string to append after query result.
     * @return String
     */
    public final String getStringAfter() {
        return stringAfter;
    }

    /**
     * Get path tp interfaces.
     * @return String
     */
    public final String getPathToInterfaces() {
        return pathToInterfaces;
    }

    /**
     * Set working path, under which project will be run.
     * @param _path new path
     * @return boolean
     */
    public boolean setPath(final String _path) {
        if ( _path != null && _path.equals(path) ) return true;
        path = (_path == null ? "." : _path);
        if ( path.startsWith("\"") ) path = path.substring(1);
        if ( path.endsWith("\"") ) path = path.substring(0, path.length()-1);

        // check if directory exists
        File f = new File(path);
        if ( !f.isDirectory() ) {
            System.out.println("ERROR: Directory " + path + " doesn't exist.");
            return false;
        }

        entriesSource = new File(path + File.separator + (bSqllst ? "sql.lst" : (".svn"+File.separator+"entries")));
        return true;
    }

    /**
     * Convert server path from sybase alias to format ip-address:port.
     * Unix-like: restore by $SYBASE/interfaces
     * Windows: restore by %SYBASE%\ini\sql.ini
     * under UNIX:
     * ----------
     * SERVERNAME
     * query	tcp 	ether	hostname	port

     * under WINDOWS:
     * -------------
     * [SERVERNAME]
     * query=NLWNSCK,host,port
     * win3_query=WNLWNSCK,host,port
     *  @throws JbsqlException If path to interfaces is not found
     */
    private void convertServerAliasToAddress() throws JbsqlException {
        // verify if servername already contains port
        if ( this.serverName.indexOf(":") != -1 ) return;
        boolean isUnix = "HP-UX".equals(System.getProperty("os.name")) || "Linux".equals(System.getProperty("os.name"));

        // define path to interfaces unless it's explicitly set
        if ( this.pathToInterfaces == null ) {
            if ( System.getProperty("SYBASE") == null ) throw new JbsqlException("SVN_05", "SYBASE", null);
            if ( isUnix ) this.pathToInterfaces = System.getProperty("SYBASE") + File.separator + "interfaces";
            else this.pathToInterfaces = System.getProperty("SYBASE") + File.separator + "ini" + File.separator + "sql.ini";
        }

        // read aliases
        String s;
        String tokens[];
        int posAlias;
        BufferedReader is = null;
        boolean bFound = false;

        final String delimiter = isUnix ? "\\s" : ",";   // unix uses SPACE or TAB delimiter; windows uses COMMA
        final int expectedTokensNum = isUnix ? 5 : 3;
        final int idxServerToken = isUnix ? 3 : 1;
        final int idxPortToken = isUnix ? 4 : 2;

        try {
            is = new BufferedReader(new InputStreamReader(new FileInputStream(new File(this.pathToInterfaces)), "UTF8"));
            while ( (s=is.readLine()) != null ) {
                s = s.trim();
                if ( s.length() == 0 || s.charAt(0) == '#' ) continue;

                if ( bFound ) {
                    // tokenize string
                    tokens = s.split(delimiter);
                    if ( tokens.length == expectedTokensNum ) {
                        // verify if string is valid:
                        // win: token[1] = IP, token[2] = port
                        // unix: token[3] = IP, token[4] = port
                        this.serverName = tokens[idxServerToken] + ":" + tokens[idxPortToken];
                        return;
                    }
                    else throw new JbsqlException("SVN_06", s, null);
                }
                // search server alias
                if ( isUnix ) bFound = s.equals(this.serverName);
                else bFound = (posAlias = s.indexOf(this.serverName)) != -1 && posAlias == 1 && s.charAt(posAlias + this.serverName.length()) == ']';
            }
            if ( !bFound ) throw new JbsqlException("SVN_07", this.serverName + " at " + this.pathToInterfaces, null);
        }
        catch (FileNotFoundException ex) { System.out.println("ERROR: failed to open interfaces file."); }
        catch (IOException ex) { System.out.println("ERROR: failed to open interfaces file."); }
        finally {
            if ( is != null ) {
                try { is.close(); }
                catch(IOException ex) { /* */ }
            }
        }
    }
}
